import java.util.ArrayList;
import java.util.Random;


public class Individual_Factory {

	int maxDepth = 18;
	static double functionProbability = 0.75;
	static double varProb = 0.5;

	// Cart-Centering
//		static int numberOfFunctions = 17;
//		static int numberOfO1Functions = 3;
//		static int numberOfO2Functions = 13;
//		static int numberOfO3Functions = 1;

	// Symbolic Regression 
	static int numberOfFunctions = 9;
	static int numberOfO1Functions = 3;
	static int numberOfO2Functions = 6;
	static int numberOfO3Functions = 0;

	static int numVars = 6;
	
	Random rand = null;

	public Individual_Factory(int treeSize, Random r){
		maxDepth = treeSize;
		rand = r;
	}

	public Individual getIndividual(){
		Individual returner = new Individual(maxDepth);
		returner.root = getNode();
		while (returner.root instanceof Value) {
			returner.root = getNode();
		}
		int result = growNode(returner.root, maxDepth-1);
//		System.out.println(returner.root.size + " " + result);
		
		return returner;
	}

	private int growNode(Node here, int levelsLeft){
		int newSize = 0;
		if(levelsLeft>=2){
			for(int k=0; k<here.order; k++){
				here.children.add(getNode());
				here.children.get(k).parent = here;
				newSize+=1;
				
				if(here.children.get(k) instanceof Value){
					here.updateSize();
				} else {
					newSize+=growNode(here.children.get(k), levelsLeft-1);
				}
			}
		}else if (levelsLeft == 1){
			for(int k=0; k<here.order; k++){
				here.children.add(getValue());
				here.children.get(k).parent = here;
				here.updateSize();
				newSize+=1;
			}
		}
		return newSize;
	}

	private Node getNode(){

		Random rand = new Random();
		double x = rand.nextDouble();
		if(x<functionProbability){
			//make a function
			x = rand.nextDouble()*numberOfFunctions;

			if (numberOfFunctions == 17) {

				if (x < 1)
					return new AbsoluteValueFunction();
				else if (x < 2)
					return new 	CosineFunction();
				else if (x < 3)
					return new 	SineFunction();
				else if (x < 4)
					return new 	AddFunction();
				else if (x < 5)
					return new 	AndFunction();
				else if (x < 6)
					return new 	DivideFunction();
				else if (x < 7)
					return new 	EqualsFunction();
				else if (x < 8)
					return new 	GreaterThanFunction();
				else if (x < 9)
					return new 	LessThanFunction();
				else if (x < 10)
					return new 	LogFunction();
				else if (x < 11)
					return new 	MultiplyFunction();
				else if (x < 12)
					return new	OrFunction();
				else if (x < 13)
					return new 	PowerFunction();
				else if (x < 14)
					return new SubtractFunction();
				else
					return new 	IfFunction();

			} else if (numberOfFunctions == 9) {

				if (x < 1)
					return new AbsoluteValueFunction();
				else if (x < 2)
					return new 	CosineFunction();
				else if (x < 3)
					return new 	SineFunction();
				else if (x < 4)
					return new 	AddFunction();
				else if (x < 5)
					return new 	DivideFunction();
				else if (x < 6)
					return new 	LogFunction();
				else if (x < 7)
					return new 	MultiplyFunction();
				else if (x < 8)
					return new 	PowerFunction();
				else
					return new SubtractFunction();

			} else {
				return null;
			}

		}else{
			return getValue();
		}

	}

	public Value getValue(){
		//make a value
		double x;
		Random rand = new Random();
		Value returner;

		x = rand.nextDouble();
		if(x<varProb && numVars>0){
			//make it a variable
			int vIndex = 0;
			if(numVars > 1)
				vIndex = (int) Math.floor((rand.nextDouble()*numVars));
			returner = new Value(true,0,vIndex);
		}else{
			//value
			double newValue = rand.nextDouble()*201-100;
			returner = new Value(false, newValue, -1);
		}

		return returner;
	}

	public Node getMatchingNode(Node old) {
		Random rand = new Random();
		double x = 0;
		int order = old.order;
		
		Function returner;

		if(order<0 || order >100){
			System.out.println("uh, someone thought you could have a node of order "+order+". How odd.");
		}

		if(order == 0){
			return getValue();
		}else if(order == 1){
			x = rand.nextDouble()*numberOfO1Functions;

			if (numberOfFunctions == 17 || numberOfFunctions == 9) {
				if (x < 1)
					returner = new AbsoluteValueFunction();
				else if (x < 2)
					returner = new 	CosineFunction();
				else 
					returner = new 	SineFunction();
			} else {
				return null;
			}

		}else if(order == 2){
			x = rand.nextDouble()*numberOfO2Functions;

			if (numberOfFunctions == 17) {
				if (x < 1) 
					returner = new AddFunction();
				else if (x < 2)
					returner = new AndFunction();
				else if (x < 3)
					returner = new 	DivideFunction();
				else if (x < 4)
					returner = new 	EqualsFunction();
				else if (x < 5)
					returner = new 	GreaterThanFunction();
				else if (x < 6)
					returner = new 	LessThanFunction();
				else if (x < 7)
					returner = new 	LogFunction();
				else if (x < 8)
					returner = new 	MultiplyFunction();
				else if (x < 9)
					returner = new 	OrFunction();
				else if (x < 10)
					returner = new 	PowerFunction();
				else 
					returner = new SubtractFunction();
			} else if (numberOfFunctions == 9) {
				if (x < 1)
					returner = new 	AddFunction();
				else if (x < 2)
					returner = new 	DivideFunction();
				else if (x < 3)
					returner = new 	LogFunction();
				else if (x < 4)
					returner = new 	MultiplyFunction();
				else if (x < 5)
					returner = new 	PowerFunction();
				else
					returner = new SubtractFunction();
			} else {
				return null;
			}
			
		}else{
			returner = new IfFunction();
		}
		
		
		returner.children = new ArrayList<Node>();
		returner.children.addAll(old.children);
		returner.parent = old.parent;
		returner.size = old.size;
		
		for (Node n : returner.children) {
			n.parent = returner;
		}
		
		return returner;

	}

}
